#pragma once

#include <cstdint>
#include <cstring>

#include "../util/lock.h"
#include "../util/atomicbitset.h"
#include "file.h"

namespace kvalloc {

// 8-byte SSD address
union FileAddr {
    struct {
        uint64_t  slabing  : 1;
        uint64_t  warm     : 1;
        uint64_t  slabid  : 47;
        uint64_t  slaboff : 15; // offset in 32 KiB
    };
    uint64_t raw;
    FileAddr(uint64_t addr = 0) {raw = addr;}
    inline uint64_t FileOff() {return slabid * SLAB_SIZE + slaboff;}
};

// the state of a slab
enum SlabState {
    FULL,
    NONFULL,
};

// The meta of slab: 88 Bytes (per 32KiB slab, needs 2.7 MiB NVM for 1GiB SSD space)
class SlabMeta {
public:
    // meta data for linklist
    uint64_t next;

    // meta data of the slab
    int16_t state_;
    SpinLock lock_;
    std::atomic_uint16_t count_;
    int16_t size_class_;
    uint64_t slabid_;
    
    // bitmap of the slab
    FixedAtomicBitset<512> bitmap_;

public:
    SlabMeta() : next(0), count_(0) {}
    SlabMeta(const SlabMeta &other) {memcpy((void *)&(other.next), &(this->next), sizeof(SlabMeta));}
    SlabMeta &operator=(const SlabMeta &other) { memcpy((void *)&(other.next), &(this->next), sizeof(SlabMeta)); return *this;}

    // allocate a slot atomically
    bool Alloc(FileAddr & addr);

    // free a slot atomically
    void Free(FileAddr addr);

    inline bool Empty() {return count_.load() == 0;}

    inline bool Full() { return count_.load() == (SLAB_SIZE / size_class_);}

    inline bool TryLock() {return lock_.TryLock();}

    inline void Lock() { lock_.Lock();}

    inline void UnLock() { lock_.UnLock();}
};

// a simple doubly linklist: without thread safty
// maintaining per-sizeclass and per-thread non-full 
class SlabList {
public:
    // volatile
    uint32_t  count_;
    SpinLock  mu_;

private:
    // persisted
    uint64_t head_;
    uint64_t tail_;

public:
    SlabList() : count_(0), head_(0), tail_(0) {}

    // return the first element
    inline SlabMeta * Front(SlabMeta * slab_pool) {return &slab_pool[head_];}

    // pop the first element at the head
    SlabMeta * PopFront(SlabMeta * slab_pool);

    // push a new element to the tail
    void PushBack(SlabMeta * slab_pool, uint64_t slabid);

    // append a list into this list
    void Append(SlabMeta * slab_pool, SlabList & other);

    // clear the list
    void Clear() { head_ = tail_ = 0; count_ = 0;}
};

} // namespace kvalloc